package com.atguigu.tingshu.order.helper;

import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.MD5;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
public class SignHelper {

    private static String signKey = "atguigu123";
    /**
     * 验签方法
     * 时效性校验：验证请求是否在有效时间内，防止过期请求被重复使用（防重放攻击）。
     * 一致性校验：验证请求中的签名是否与本地计算的签名一致，确保参数未被篡改。
     * @param parameterMap
     */
    public static void checkSign(Map<String, Object> parameterMap){
        //校验签名时间
        //首先检查请求中是否包含 timestamp（时间戳）参数，若不存在则直接判定签名错误。
        //（时间戳是客户端生成请求时的时间，用于标记请求的发送时间）
        Long remoteTimestamp = (Long)parameterMap.get("timestamp");
        if(StringUtils.isEmpty(remoteTimestamp)){
            throw new GuiguException(ResultCodeEnum.SIGN_ERROR);
        }
        //若时间差超过 600000 毫秒（即 10 分钟），则判定签名过期。
        //作用：防止攻击者截获有效的请求签名后，在长时间内重复发送该请求（重放攻击），通过时间窗口限制降低风险。
        long currentTimestamp = getTimestamp();
        if (Math.abs(currentTimestamp - remoteTimestamp) > 600000) {
            log.error("签名已过期，服务器当前时间:{}", currentTimestamp);
            throw new GuiguException(ResultCodeEnum.SIGN_OVERDUE);
        }

        //校验签名，获取请求中携带的签名（客户端生成的签名）
        String signRemote = (String)parameterMap.get("sign");
        //使用本地相同的算法重新计算签名（基于当前请求参数）
        String signLocal = getSign(parameterMap);
        //请求中没有签名，直接报错
        if(StringUtils.isEmpty(signRemote)){
            throw new GuiguException(ResultCodeEnum.SIGN_ERROR);
        }

        //本地计算的签名与请求携带的签名不一致，说明参数可能被篡改，抛出签名错误异常
        if(!signRemote.equals(signLocal)){
            throw new GuiguException(ResultCodeEnum.SIGN_ERROR);
        }
    }

    /**
     * 请求数据获取签名,根据参数集合生成唯一的 MD5 签名，确保数据在传输过程中未被篡改
     * @param parameterMap
     * @return
     */
    public static String getSign(Map<String, Object> parameterMap) {
        //去掉sign参数
        if(parameterMap.containsKey("sign")) {
            //签名是基于其他业务参数生成的，不能包含签名本身，否则会导致循环依赖问题
            parameterMap.remove("sign");
        }

        //有序
        //使用 TreeMap 对参数按键名自然顺序排序
        //关键作用：保证参数顺序完全一致，避免因参数顺序不同而生成不同的签名（例如 {"a":1,"b":2} 和 {"b":2,"a":1} 需生成相同签名）
        TreeMap<String, Object> sorted = new TreeMap<>(parameterMap);
        StringBuilder str = new StringBuilder();
        for (Map.Entry<String, Object> param : sorted.entrySet()) {
            //获取键值对中的值,将其拼接起来
            str.append(param.getValue()).append("|");
        }
        //最后连接signKey，在拼接字符串末尾添加双方约定的signKey（密钥）
        //这是防止签名被伪造的关键：只有知道密钥的双方才能生成相同的签名
        str.append(signKey);
        log.info("加密前：" + str.toString());
        String md5Str = MD5.encrypt(str.toString());//不可逆加密算法
        log.info("加密后：" + md5Str);
        return md5Str;

        /*
        例子：
            参数：{name:"课程A", price:199, timestamp:1620000000}
            排序后：{name:"课程A", price:199, timestamp:1620000000}
            拼接值："课程A|199|1620000000|"
            加上密钥signKey="abc123"："课程A|199|1620000000|abc123"
            MD5 加密后得到签名：例如"3e89f8d7a3c5b1e2f4a6d8c0b9e7f6"
         */
    }

    /**
     * 获取时间戳
     * @return
     */
    public static long getTimestamp() {
        return new Date().getTime();
    }

}
